package uebung02_w;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;

import tools.Arrow;
import tools.Gauss;



public class Aufgabe03_w {
	
	/*
	 * 3. Aufgabe (12 Punkte)
	 * 
	 * Gegeben die diskreten Pixelkoordinaten (Mitte des Bildes (abgerundet)), 
	 * eine Orientierung (0°) und ein Bild, berechnen Sie den SIFT-Deskriptor. 
	 * Nehmen Sie an, das Bild ist schon in der richtigen Skalierungsstufe gegeben. 
	 * D.h kein Gaussfilterung des Bildes notwendig. Die Gewichtung der Gradienten 
	 * erfolgt jedoch einem Gausskernel mit halber breite des Histogramm-Fensters.
	 * 
	 */
	
	
	
	public static void main(String[] args) {
		SIFTDescriptor.main(args);
		try {
			
			/* bild in richtiger skalierungsstufe nach gausspyramide, dog, ... */
			final BufferedImage bimg = ImageIO.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("res/kermit001.jpg"));
			final int w = bimg.getWidth(), h = bimg.getHeight();
//			final BufferedImage subimg = bimg.getSubimage(x, y, w, h);
			final int[] rgbimg = bimg.getRGB(0, 0, w, h, new int[w*h], 0, w);

			/* helligkeits bild berechnen */
			final int[] lumimg = tools.ColorConversions.getFastLuminance(rgbimg, new int[w*h]);
			
			/* ableitungen dx, dy des helligkeitsbildes */
			int[] dx = tools.Derivative.dx(lumimg, w, h, new int[w*h]);
			int[] dy = tools.Derivative.dy(lumimg, w, h, new int[w*h]);
			
			/* winkel und betraege berechnen */
			double[] theta = tools.Derivative.getAngles(dx, dy, new double[w*h]);
			double[] norm = tools.Derivative.getLengths(dx, dy, new double[w*h]);
			
			/*
			 * 16x16 pixel auschneiden um den featurepoint im bild der richtigen skalierung (und winkel)
			 * helligkeitsbild
			 * gauss
			 * gradienten = dx, dy
			 * rotating gradients through alpha
			 * 4x(4x4) subregions
			 * gauss auf subregions (gewichten der gradienten)
			 * 8x45degree histogram for each subregion (with trilinear interpolation)
			 * => 4x4x8 vector
			 * normalize
			 * cap 0.2
			 * normalize
			 * 
			 */
			AffineTransform mapping = AffineTransform.getTranslateInstance(w/2, h/2);
			
			final SIFTDescriptor sifty = new SIFTDescriptor(new AffineTransform());
			Rectangle2D selection = new Rectangle2D.Double();
			
			//transformiertes Siftcenter
			Point2D centerD = new Point2D.Double();
			centerD.setLocation(sifty.getCenter());
			
			centerD = mapping.transform(centerD, centerD);
			
			//den deskriptor durchgehen

			for (int x = -2; x<2; x++) {
				for (int y = -2; y<2; y++) {
					//einzelnes Histogrammfenster setzen:
					selection.setFrame(x*4, y*4, 4, 4);

					//transformieren
					Shape transSelection = mapping.createTransformedShape(sifty.getTransform().createTransformedShape(selection));
					
					//bounds holen um Pixel zu finden die in der Umgebung von selection liegen
					Rectangle bounds = transSelection.getBounds();

					double[] histo = new double[8];
					//Zentrum des Histogrammfensters
					Point2D centerH = new Point2D.Double(selection.getCenterX(), selection.getCenterY());
					
					centerH = sifty.getTransform().transform(centerH, centerH);
					centerH = mapping.transform(centerH, centerH);

					//hilfspunkt der die Pixelkoordinaten annimmt
					Point2D p = new Point2D.Double();
					Point2D rot = new Point2D.Double();
					//diese Pixel fallen in die bounds der Auswahl
					for (int j = (int)Math.floor(bounds.getMinY()), endy = (int)Math.ceil(bounds.getMaxY()); j<endy; j++) {
						for (int i = (int)Math.floor(bounds.getMinX()), endx = (int)Math.ceil(bounds.getMaxX()); i<endx; i++) {
//							System.out.println(i+" "+j);

							//p auf die Koordinaten setzen
							p.setLocation(i,j);
//							System.out.println(i+" "+j);

							//schauen ob pixel p(i,j) in der Auswahl ist
							if (transSelection.contains(p)) {
								//calculating histogram values
								for (int l=0; l<8; l++) {
									double g = Gauss.gaussian1d(p.distance(centerD), sifty.getWidth()/2.);
									double trilinear = 1-(p.distance(centerH)/sifty.getHistogramNorm()); 

									rot.setLocation(1,0);
									sifty.getTransform().transform(rot, rot);
									
									double x_ = rot.getX()-sifty.getCenter().getX(), y_ = rot.getY()-sifty.getCenter().getY();
									double r = Math.sqrt(x_*x_+y_*y_);
									
									x_ /= r;
									y_ /= r;
									
									double angle = Math.toDegrees(Math.atan2(y_,x_));
									double omega = l*45.+angle-Math.toDegrees(theta[i+j*w]);
									
									while (omega<0)
										omega += 360;
									while (omega >360)
										omega -= 360;
									
									double et = 1-(Math.abs(omega/45.));
									
									double e = Math.max(0, et)*norm[i+j*w];

									double value = g*trilinear*e;
//									System.out.println("gauss: "+g+" trilinear: "+trilinear+" e: "+e+" et: "+et+" "+norm[i+j*w]);
									int index = (x+2)*8+(y+2)*8*4+l;
									sifty.add(index, value);
								}
							}
						}
					}		
				}
			}
//			System.out.println(sifty);

			sifty.normalize();
			sifty.capAndNormalize();
			System.out.println(sifty);
			JFrame ff = new JFrame();
			ff.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			
			Point2D a = new Point2D.Double(100,100), b = new Point2D.Double(500,300);
			
			double arrowWidth = 10, arrowHeight = 40;
			
			final Arrow arrw = new Arrow();
			arrw.setArrow(a, b, arrowWidth, arrowHeight);
			
			final AffineTransform zoom = new AffineTransform();
			final AffineTransform rotate = new AffineTransform();
			final Point2D centre = new Point2D.Double(100,100);
			
			final JComponent canvas = new JComponent() {
				{setPreferredSize(new Dimension(800,800));}
				Line2D line = new Line2D.Double();
				final double scale = 100;
				@Override
				public void paint(Graphics g) {

					Graphics2D g2 = (Graphics2D)g;
					g2.setStroke(new BasicStroke(0.3f));
				    g2.transform(zoom);
				    g2.setColor(Color.white);
			    	g2.fillRect(0, 0, this.getWidth(), this.getHeight());

					g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				    g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
				    g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				    
				    Point2D point = new Point2D.Double();
				    Point2D centransform = new Point2D.Double();
				    
				    for (int j = 0; j < sifty.size()/8; j++) {
				    	
				    	double shiftx = (j%4)*200, shifty = (j/4)*200; 
				    	line.setLine(0, shifty, 800, shifty);
				    	g2.setColor(Color.cyan);
				    	if (Double.compare(shifty, 0) != 0)
				    		g2.draw(line);
				    	line.setLine(shiftx, 0, shiftx, 800);
				    	if (Double.compare(shiftx, 0) != 0)
				    		g2.draw(line);
				    	
				        for (int i = 0; i <8; i++) {
							point.setLocation(1,0);
							double rot = Math.toRadians(i*45);
							rotate.setToRotation(rot);
							rotate.transform(point, point);

							point.setLocation((sifty.get(j*8+i)/sifty.getMax())*(scale)*point.getX()+centre.getX()+shiftx, (sifty.get(j*8+i)/sifty.getMax())*(scale)*point.getY()+centre.getY()+shifty);
//							System.out.println(point);
							centransform = new Point2D.Double(centre.getX()+shiftx, centre.getY()+shifty);

							if (Double.compare(point.distanceSq(centransform),0)!=0) {
								arrw.setArrow(centransform, point, 3, 10);
							    g2.setColor(Color.magenta);
								g2.fill(arrw);
								g2.setColor(Color.black);
								g2.draw(arrw);
							}
							else {
								point.setLocation(1,0);
								rotate.transform(point, point);
								point.setLocation(point.getX()+shiftx+centre.getX(), point.getY()+shifty+centre.getY());
								arrw.setArrow(centransform, point, 3, 10);
							    g2.setColor(Color.green);
								g2.fill(arrw);
								g2.setColor(Color.black);
								g2.draw(arrw);
							}
						}
						
					}
				    
					super.paint(g);
				}
			};
			canvas.addMouseWheelListener(new MouseWheelListener() {
				
				@Override
				public void mouseWheelMoved(MouseWheelEvent e) {
					
					double scale = Math.pow(1.01, e.getWheelRotation());

					try {
						Point2D p = new Point2D.Double(e.getX(), e.getY()); 
						zoom.inverseTransform(p, p);
						zoom.concatenate(AffineTransform.getTranslateInstance(p.getX(), p.getY()));
						zoom.concatenate(AffineTransform.getScaleInstance(scale, scale));
						zoom.concatenate(AffineTransform.getTranslateInstance(-p.getX(), -p.getY()));
					}
					catch (NoninvertibleTransformException nte ) {
						nte.printStackTrace();
					}
					canvas.repaint();
				}
			});
			
			ff.add(canvas);
			
			
			ff.pack();
			ff.setVisible(true);
		} // end try 
		catch (IOException e) { e.printStackTrace(); }

	} // method main()
	
	



} /* END class Aufgabe03_w */
